package com.itcoon.cloud.framework.loadbalancer.core;

import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.itcoon.cloud.framework.utils.IPUtil;
import com.itcoon.cloud.framework.utils.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
import org.springframework.cloud.client.loadbalancer.EmptyResponse;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.Response;
import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import reactor.core.publisher.Mono;

import java.util.*;
import java.util.concurrent.ThreadLocalRandom;

/**
 * 改均衡器优先级是important> same ip > 其他
 * 当设置nacos matedata属性important=true时，该实例优先权最高，这对线上强制调用本地实例非常有用
 * 当不满足第一种情况下，具有相同ip的实例优先级更高，这对于本地调用本地非常有用
 * 当前面不满足，则随机访问集群其他实例
 */

public class NacosLocalFirstLoadBalancer implements ReactorServiceInstanceLoadBalancer {

    private static final Logger log = LoggerFactory.getLogger(NacosLocalFirstLoadBalancer.class);

    private final String serviceId;

    private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;

    private final NacosDiscoveryProperties nacosDiscoveryProperties;

    private Set<String> localIps;

    public NacosLocalFirstLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId, NacosDiscoveryProperties nacosDiscoveryProperties) {
        this.serviceId = serviceId;
        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
        this.nacosDiscoveryProperties = nacosDiscoveryProperties;
        this.localIps = Collections.singleton(IPUtil.getServerIP());
    }

    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
                .getIfAvailable(NoopServiceInstanceListSupplier::new);
        return supplier.get().next().map(this::getInstanceResponse);
    }

    /**
     * 优先获取与本地IP一致的服务，否则随机获取服务
     */
    private Response<ServiceInstance> getInstanceResponse(
            List<ServiceInstance> serviceInstances) {
        if (serviceInstances.isEmpty()) {
            log.warn("No servers available for service: " + this.serviceId);
            return new EmptyResponse();
        }

        // metadata.important == true 优先
        Optional<ServiceInstance> serviceInstanceOp = serviceInstances.stream().filter(s->Boolean.parseBoolean(s.getMetadata().getOrDefault("important", "false"))).findAny();
        if(serviceInstanceOp.isPresent()){
            ServiceInstance instance = serviceInstanceOp.get();
            log.info("[ loadbalancer ] - Find an service instance with metadata property (important = true): host = {}, port = {}, uri = {}", instance.getHost(), instance.getPort(), instance.getUri());
            return new DefaultResponse(instance);
        }

        // 同IP优先
        Map<String, ServiceInstance> serviceInstanceMap = MapUtils.map(serviceInstances, ServiceInstance::getHost);
        Set<String> hosts = serviceInstanceMap.keySet();
        hosts.retainAll(localIps);
        if(!hosts.isEmpty()){
            ServiceInstance instance = serviceInstanceMap.get(hosts.iterator().next());
            log.info("[loadbalancer] - Find an service instance with local ip: host = {}, port = {}, uri = {}", instance.getHost(), instance.getPort(), instance.getUri());
            return new DefaultResponse(instance);
        }
        int index = ThreadLocalRandom.current().nextInt(serviceInstances.size());
        ServiceInstance instance = serviceInstances.get(index);
        return new DefaultResponse(instance);
    }
}


